當我們在設計分散式系統,通常是圍繞在有多台節點(Node),只要系統跨了多個 Node、Replica 或 Region 後,我們就無法假設節點之間的網路永遠可靠。網路可能延遲、封包遺失,甚至暫時完全無法互相通訊。
畢竟你無法保證你家的機房會不會有老鼠拜訪把網路線當點心。
所以分散式系統,網路分區是必然的,其中就一定會談談到 CAP Theorem(CAP 定理),它在說:
當系統發生網路分割(Partition)時,你不可能同時保證「強一致性」和「高可用性」,必須做取捨。
而 CAP 三個字母分別代表:
balance = 100,下一次讀取就不應該再看到舊的 balance = 200。CAP 不是「三個挑兩個」這麼簡單。實際分散式系統通常必須接受 P,因為網路故障無法避免。所以真正的問題通常是:
在 Partition 必然發生的情況下,我要選 C 還是 A?
再白話一點的說法是:
當某個節點拿不到最新資料時,這條 Request 應該拒絕,還是先回應一份可能不是最新的資料(Stale Data)?
例如支付系統的「扣款」通常偏 CP:如果無法確認最新餘額,寧可暫時拒絕交易,也不能讓兩台服務都認為餘額足夠而重複扣款。
但像「商品瀏覽數、按讚數、推薦資料」可能偏 AP:即使短暫看到舊資料也沒關係,重點是服務不要掛掉。例如社群軟體,你不會因為刷不到泰勒絲最新的 IG 動態而感覺服務壞掉。
所以你在 System Design 裡看到 CAP,核心其實是在問:
「當分散式系統的節點失聯時,你要犧牲資料即時一致性,還是犧牲服務可用性?」
然而在分散式系統下 CP 跟 AP 並非只能選擇一種,而是可以混搭,意思是你可以依據不同功能去決定是否要強調一致性或是可用性。
且 AP 不代表資料永遠亂掉,CP 也不代表永遠不可用。
AP 系統通常會搭配 Eventual Consistency(最終一致性),允許 Replica 在短時間內存在不同版本,但在沒有持續新寫入的情況下,最終讓資料收斂,講求最後正確即可;也可以通過讀取修復(read repair),在讀取的時候順手修復舊的 replica。
CP 可以限制在某些操作才會被拒絕或暫停,拒絕某些 Critical Operation。例如扣款暫時失敗,但查詢交易紀錄仍然可以正常使用。
一筆付款已經在 Node A 完成,但因為 Network Partition,Node B 無法確認最新餘額。這時應該繼續允許扣款,還是拒絕交易?為什麼?
使用者剛修改大頭貼,但另一個 Region 仍然顯示舊照片。你會選擇讓整個 Profile 暫時無法開啟,還是先顯示舊照片?
同一套電商系統中,「商品列表」可以接受舊庫存,但「Checkout 扣庫存」不能接受舊庫存。這個系統到底算 CP 還是 AP?